import logging
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, Optional

import emails
from emails.template import JinjaTemplate
from jose import jwt

from app.core.config import settings


def send_email(
        email_to: str,
        subject_template: str = "",
        html_template: str = "",
        environment: Dict[str, Any] = {},
) -> None:
    assert settings.EMAILS_ENABLED, "no provided configuration for email variables"
    message = emails.Message(
        subject=JinjaTemplate(subject_template),
        html=JinjaTemplate(html_template),
        mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
    )
    smtp_options = {"host": settings.SMTP_HOST, "port": settings.SMTP_PORT}
    if settings.SMTP_TLS:
        smtp_options["tls"] = True
    if settings.SMTP_USER:
        smtp_options["user"] = settings.SMTP_USER
    if settings.SMTP_PASSWORD:
        smtp_options["password"] = settings.SMTP_PASSWORD
    response = message.send(to=email_to, render=environment, smtp=smtp_options)
    logging.info(f"send email result: {response}")


def send_test_email(email_to: str) -> None:
    project_name = settings.PROJECT_NAME
    subject = f"{project_name} - Test email"
    with open(Path(settings.EMAIL_TEMPLATES_DIR) / "test_email.html") as f:
        template_str = f.read()
    send_email(
        email_to=email_to,
        subject_template=subject,
        html_template=template_str,
        environment={"project_name": settings.PROJECT_NAME, "email": email_to},
    )


def send_reset_password_email(email_to: str, email: str, token: str) -> None:
    project_name = settings.PROJECT_NAME
    subject = f"{project_name} - Password recovery for user {email}"
    with open(Path(settings.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
        template_str = f.read()
    server_host = settings.SERVER_HOST
    link = f"{server_host}/reset-password?token={token}"
    send_email(
        email_to=email_to,
        subject_template=subject,
        html_template=template_str,
        environment={
            "project_name": settings.PROJECT_NAME,
            "username": email,
            "email": email_to,
            "valid_hours": settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
            "link": link,
        },
    )


def send_new_account_email(email_to: str, username: str, password: str) -> None:
    project_name = settings.PROJECT_NAME
    subject = f"{project_name} - New account for user {username}"
    with open(Path(settings.EMAIL_TEMPLATES_DIR) / "new_account.html") as f:
        template_str = f.read()
    link = settings.SERVER_HOST
    send_email(
        email_to=email_to,
        subject_template=subject,
        html_template=template_str,
        environment={
            "project_name": settings.PROJECT_NAME,
            "username": username,
            "password": password,
            "email": email_to,
            "link": link,
        },
    )


def generate_password_reset_token(email: str) -> str:
    delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
    now = datetime.utcnow()
    expires = now + delta
    exp = expires.timestamp()
    encoded_jwt = jwt.encode(
        {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, algorithm="HS256",
    )
    return encoded_jwt


def verify_password_reset_token(token: str) -> Optional[str]:
    try:
        decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
        return decoded_token["email"]
    except jwt.JWTError:
        return None


def deal_menus(menu):
    """菜单Model生成dict Tree"""
    meta = {
        'title': menu.title,
        "icon": menu.icon,
        "noCache": menu.noCache,
        "affix": menu.affix,
        "order": menu.order
    }
    if menu.children:
        children = [deal_menus(menu) for menu in menu.children]
        children.sort(key=lambda x: x["meta"]["order"])
        menu_dict = menu.dict()
        menu_dict['children'] = children
    else:
        menu_dict = menu.dict()
    menu_dict['meta'] = meta
    menu_dict['meta']["roles"] = [role.role_key for role in menu.roles]
    return menu_dict


def menus_to_list(menus):
    """菜单dict生成list"""
    if menus.get("children", []) == []: return [menus, ]
    children = [menus_to_list(menu)[0] for menu in menus['children']]
    menus.pop("children")
    menus = [menus, ]
    menus.extend(children)
    return menus


def tree_children(node):
    if node.children == []: return node.dict()
    node_dict = node.dict()
    node_dict["children"] = [tree_children(child) for child in node.children]
    return node_dict


def list_to_tree(node_list, root_id=None):
    node_dict = {node["id"]: node if node.get("parent_id") else node.update({"parent_id": -1}) or node for node in node_list}
    for node in node_list:
        node_dict.setdefault(node["parent_id"], {}).setdefault("children", []).append(node)
    return node_dict[root_id if root_id else -1]


def get_list_id_by_tree(nodes):
    ids = [nodes["id"], ]
    if nodes.get("children"):
        for node in nodes["children"]: ids = ids + get_list_id_by_tree(node)
    return ids


def round_float(float_num,num=2):
    import decimal
    # 四舍五入
    context = decimal.getcontext()
    context.rounding = decimal.ROUND_HALF_UP
    return float(round(decimal.Decimal(str(float_num)), num))
